//
// Author: Vladimir Migashko <migashko@gmail.com>, (C) 2011
//
// Copyright: See COPYING file that comes with this distribution
//

#ifndef FASYS_CHANNELS_MESSAGES_CHANNELS_CONTAINER_HPP
#define FASYS_CHANNELS_MESSAGES_CHANNELS_CONTAINER_HPP

#include "types.hpp"
#include "tags.hpp"
#include "message_info.hpp"

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/composite_key.hpp>

#include <list>

namespace fasys{ namespace channels{

using namespace boost;
using namespace boost::multi_index;

/// //////////////////////////////////////////////////

// Теги мультииндекса
/*struct _message_id_;
struct _channel_id_;
struct _order_;
struct _death_time_;
*/

/// //////////////////////////////////////////////////

struct message_ordered_by_message_id
  : ordered_unique<
      tag<_message_id_>,
      composite_key<
        message_info,
        member<message_info, channel_id_t, &message_info::channel_id>,
        member<message_info, message_id_t, &message_info::message_id>
      >
   >
{
};

/*
struct message_ordered_by_channel_id
  : ordered_unique<
      tag<_channel_id_>,
      composite_key<
        message_info,
        member<message_info, channel_id_t, &message_info::channel_id>,
        member<message_info, message_id_t, &message_info::message_id>
      >
   >
{
};
*/

struct message_ordered_by_order
  : ordered_non_unique<
      tag<_order_>,
      composite_key<
        message_info,
        member<message_info, channel_id_t, &message_info::channel_id>,
        member<message_info, order_t, &message_info::order>,
        member<message_info, time_t, &message_info::create_time>,
        member<message_info, message_id_t, &message_info::message_id>
      >,
      composite_key_compare<
        std::less<channel_id_t>,
        std::greater<order_t>,
        std::greater<time_t>,
        std::greater<message_id_t>
      >
   >
{
};

struct message_ordered_by_death_time
  : ordered_non_unique<
      tag<_death_time_>,
      composite_key<
        message_info,
        member<message_info, time_t, &message_info::death_time>
      >
   >
{
};

/// //////////////////////////////////////////////////

struct message_indices
  : indexed_by<
      message_ordered_by_message_id,
      //message_ordered_by_channel_id,
      message_ordered_by_order,
      message_ordered_by_death_time
    >
{};

typedef multi_index_container<message_info, message_indices> message_multi_index;

/// //////////////////////////////////////////////////

// обертка над мульти индексом
class messages_channels_container
  : private message_multi_index
{
  typedef message_multi_index super;
  typedef super::index<_message_id_>::type by_message_id_type;
  typedef super::index<_order_>::type by_order_type;
  typedef super::index<_death_time_>::type by_death_time_type;

public:

  typedef std::list<message_info> message_list;

  /// @return сообщений в канале
  size_t insert( const message_info& mi )
  {
    if ( !_check_message(mi) )
      throw;

    if ( mi.limit > 0 )
    {
      std::pair<const_iterator, bool> result = super::insert(mi);

      if ( !result.second )
      {
        // TODO: обработать причину и кинуть ескепшн
        throw;
      }
    }

    return remove_excess(mi.channel_id, mi.limit);
  }

  size_t insert_or_replace( const message_info& mi )
  {
    by_message_id_type& by_message_id = super::get<_message_id_>();
    by_message_id_type::const_iterator itr = by_message_id.find( make_tuple( mi.channel_id, mi.message_id ) );

    if ( itr == by_message_id.end() )
    {
      return this->insert(mi);
    };

    if ( !_check_message(mi) )
      throw;

    if ( !by_message_id.replace(itr, mi) )
      throw;

    return remove_excess(mi.channel_id, mi.limit);
  }

  message_info get_message(channel_id_t channel_id, message_id_t message_id)
  {
    throw;
    return message_info();
  }

  void remove_death(time_t now )
  {
    by_death_time_type& by_death_time = super::get<_death_time_>();
    by_death_time_type::const_iterator itr = by_death_time.lower_bound( now );
    by_death_time.erase( by_death_time.begin(), itr );
  }

  size_t remove_excess( channel_id_t channel_id, size_t limit )
  {
    by_order_type& by_order = super::get<_order_>();
    std::pair< by_order_type::const_iterator, by_order_type::const_iterator >
        range = by_order.equal_range( channel_id );

    size_t count = 0;
    for ( ; range.first != range.second && limit != 0 ; ++range.first, --limit, ++count );

    if ( range.first != range.second && limit == 0)
      by_order.erase(range.first, range.second);

    return count;
  }

  size_t channel_size(channel_id_t channel_id)
  {
    by_message_id_type& by_message_id = super::get<_message_id_>();
    std::pair< by_message_id_type::const_iterator, by_message_id_type::const_iterator>
        range = by_message_id.equal_range( channel_id );
    return std::distance(range.first, range.second);
  }

  template<typename F>
  size_t select_by_order(F f, channel_id_t channel_id, size_t offset = 0, size_t limit = 10 ) const
  {
    const by_order_type& by_order = super::get<_order_>();
    std::pair< by_order_type::const_iterator, by_order_type::const_iterator >
        range = by_order.equal_range( channel_id );

    size_t count = 0;
    for ( ; range.first != range.second && offset != 0 ; ++range.first, ++count, --offset );
    for ( ; range.first != range.second && limit != 0 ; ++range.first, --limit, ++count )
      f(*range.first);
    for ( ; range.first != range.second; ++range.first, ++count );
    return count;
  }

  // Извлекает (удаляет) сообщения из канала
  // В F можно делать const_cast и swap data
  // return сколько сообщений осталось в канале

  template<typename F>
  size_t detach_by_order(F f, channel_id_t channel_id, size_t offset = 0, size_t limit = 10 )
  {
    by_order_type& by_order = super::get<_order_>();
    std::pair< by_order_type::iterator, by_order_type::iterator >
        range = by_order.equal_range( channel_id );

    by_order_type::iterator beg = range.first;
    by_order_type::iterator start = range.first;

    for ( ;range.first != range.second && offset != 0 ; ++range.first, ++beg, --offset );

    for ( ; range.first != range.second && limit != 0 ; ++range.first, --limit )
      f(*range.first);

    by_order.erase(beg, range.first);

    return std::distance(start, range.second);
  }

  template<typename F>
  std::pair<channel_id_t, size_t> detach_first_by_order(F f, size_t offset = 0, size_t limit = 10 )
  {
    std::pair<channel_id_t, size_t> result;
    if ( super::empty() )
      return result;

    result.first = super::begin()->channel_id;
    result.second = this->detach_by_order(f, result.first, offset, limit);
    return result;
  }


private:

  bool _check_message(const message_info& mi)
  {
    return true;
  };

};

}}

#endif
